package scenetracer.core.primitive;

import java.util.LinkedList;
import java.util.List;

import scenetracer.core.Primitive;
import scenetracer.core.Ray;
import scenetracer.core.primitive.IntersectionState.IntersectionKind;
import scenetracer.core.shader.Shader;
import scenetracer.math.BoundingBox;
import scenetracer.math.Point2;
import scenetracer.math.Point3;
import scenetracer.math.Vector3;
import scenetracer.util.Octree;

public class OctreeNode extends Primitive{
	private static final float DELTA = 0.001f; 
	private int nTriangle = 50;
	public LinkedList<OctreeNode> node;
	public BoundingBox box;
	
	public OctreeNode(Shader shader, String name,int nTriangle) {
		this.node = new LinkedList<OctreeNode>();
		this.box = new BoundingBox();
		this.nTriangle = nTriangle;
		setName(name);
		getShaders().add(shader);
	}

	public OctreeNode(BoundingBox smallVoxel, int nTriangle) {
		this.node = new LinkedList<OctreeNode>();
		this.box = smallVoxel;
		this.nTriangle = nTriangle;
	}


	public LinkedList<OctreeNode> getChildren(){
		return this.node;
	}
	
	public void addChild(OctreeNode octtree){
		this.node.add(octtree);
	}
	
	public BoundingBox getVoxel(){
		return this.box;
	}
	
	public void addMesh(){
		
	}	
	
	public void createOctTree(OctreeNode node,int depth,Shader shader){
		
		if(depth==0){
			BoundingBox leaf = node.box;
			TriangleMesh mesh = new TriangleMesh();
			mesh.getShaders().add(shader);
			leaf.setMesh(mesh);
			
			return;
		}
	        
	     LinkedList<BoundingBox> smallVoxels = box.divideOctTree();
	       
	    
	     for (BoundingBox smallVoxel : smallVoxels) {
	           OctreeNode child = new OctreeNode(smallVoxel, nTriangle);
	           node.addChild(child);
	           createOctTree(child, depth-1,shader);
	     }
	       
	     return;       
		
	}
	public void createOctTreeNoDepth(OctreeNode node){
    	BoundingBox box = node.box;
    	List<Triangle> triangles = box.mesh.getTriangles();
    	
    	if( triangles.size() < nTriangle ) {
    		return;    		
    	}
    	
    	List<BoundingBox> smallBoxes = box.divideOctTree();      
        for (BoundingBox smallBox : smallBoxes) {
        	TriangleMesh auxMesh = new TriangleMesh();
        	smallBox.setMesh(auxMesh);
        	auxMesh.setShaders(box.mesh.getShaders());
        	smallBox.setShaders(box.mesh.getShaders());
            OctreeNode child = new OctreeNode(smallBox,nTriangle);
            child.setShaders(box.mesh.getShaders());
            node.addChild(child);
        }
        
    	box.setMesh(null);        
    	for (BoundingBox smallBox : smallBoxes) {
    		for (Triangle t : triangles) {
    			if(Octree.triangleMatchThisBox(smallBox, t)){        			
        			smallBox.mesh.add(t);
                }  	         
            }    		                
        }
    	
    	List<OctreeNode> children = node.getChildren();
    	for (OctreeNode child : children) {
    		createOctTreeNoDepth(child);
        }
		
	}

	
	public IntersectionState getCloserIntersection(Point3 origin, IntersectionState i1, IntersectionState i2) {

		if( i1 == null) {
			if(i2 != null && origin.distanceTo(i2.getIntersectionPoint()) > DELTA)
				return i2;
			else
				return null;
		}
		
		if( i2 == null) {
			if(i1 != null && origin.distanceTo(i1.getIntersectionPoint()) > DELTA)
				return i1;
			else
				return null;
		}
		
		/*
		if( i1 == null)
			return i2;
		if( i2 == null)
			return i1;
		*/
		

		
		Point3 dif1 = new Point3(i1.getIntersectionPoint().x - origin.x, i1.getIntersectionPoint().y - origin.y, i1.getIntersectionPoint().z - origin.z);
		Point3 dif2 = new Point3(i2.getIntersectionPoint().x - origin.x, i2.getIntersectionPoint().y - origin.y, i2.getIntersectionPoint().z - origin.z);
		
		Vector3 i1Vec = new Vector3(dif1.x, dif1.y, dif1.z);
		Vector3 i2Vec = new Vector3(dif2.x, dif2.y, dif2.z);

		
		float i1Distance = i1Vec.length();
		float i2Distance = i2Vec.length();

		IntersectionState closer = i1;

		if(i2Distance < i1Distance){
			closer = i2;
		}
		
		return closer;

	}
	

	@Override
	public boolean Intersect(Ray ray, IntersectionState intersection) {
		IntersectionState ss= Intersect(ray.getOrigin(), ray.getDirection());
		
		if(ss==null){
			return false;
		}else{
			intersection.setIntersectionPoint(ss.getIntersectionPoint());
			intersection.setNormal(ss.getNormal());
			intersection.setIntersectionKind(IntersectionKind.OUT);
			intersection.setPrimitive(ss.getPrimitive());
			return true;
		}
	}
	
	@Override
	public Vector3 getNormal(IntersectionState intersection) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Point2 getTextureCoords(Point3 point) {
		// TODO Auto-generated method stub
		return null;
	}

	public IntersectionState Intersect(Point3 origin, Vector3 orientation) {

		BoundingBox box =	this.box;		
		if(box.intersectDistance(origin, orientation)==null) {
			return null;
		}
		
		
		
		
		List<OctreeNode> children = this.getChildren();
		
		if( children.isEmpty() ) {
			return this.box.mesh.Intersect(origin, orientation);			
		}
			
		if(children.size() != 0){
			IntersectionState inter0 = this.node.get(0).Intersect(origin,orientation);
			IntersectionState inter1 = this.node.get(1).Intersect(origin,orientation);
			IntersectionState inter2 = this.node.get(2).Intersect(origin,orientation);
			IntersectionState inter3 = this.node.get(3).Intersect(origin,orientation);
			IntersectionState inter4 = this.node.get(4).Intersect(origin,orientation);
			IntersectionState inter5 = this.node.get(5).Intersect(origin,orientation);
			IntersectionState inter6 = this.node.get(6).Intersect(origin,orientation);
			IntersectionState inter7 = this.node.get(7).Intersect(origin,orientation);
			
			IntersectionState closerPoint =
				getCloserIntersection(origin, inter0, inter1);
			
			closerPoint =
				getCloserIntersection(origin, inter2, closerPoint);
			
			closerPoint =
				getCloserIntersection(origin, inter3, closerPoint);
			
			closerPoint =
				getCloserIntersection(origin, inter4, closerPoint);
			
			closerPoint =
				getCloserIntersection(origin, inter5, closerPoint);
			
			closerPoint =
				getCloserIntersection(origin, inter6, closerPoint);
			
			closerPoint =
				getCloserIntersection(origin, inter7, closerPoint);
			
		
			return closerPoint;
		}
		
		return null;
	}
	
	

}
